package org.duang.http.core;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.concurrent.FutureCallback;
import org.duang.http.common.ClientException;
import org.duang.http.common.ResponseMessage;
import org.duang.http.core.inter.AsyncCallback;
import org.duang.http.core.inter.AsyncResult;
import org.duang.http.core.inter.ResultParser;
import org.duang.http.util.HttpUtil;
import org.duang.kit.ToolsKit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HttpCallBack<T> implements FutureCallback<HttpResponse> {

	private static Logger logger = LoggerFactory.getLogger(HttpCallBack.class);

	private boolean success = false;
	private Exception exception = null;
	private ResponseMessage responseMessage = null;
	private boolean cancalled = false;

	private ResultParser<T> resultParser;
	private AsyncCallback<T> callback;
	private DefaultAsyncResult<T> result;

	public HttpCallBack(ResultParser<T> resultParser, AsyncCallback<T> callback) {
		this.resultParser = resultParser;
		this.callback = callback;
		this.result = new DefaultAsyncResult<T>(this);
	}

	@Override
	public void completed(HttpResponse response) {
		buildResponseMessage(response);
	}

	@Override
	public void cancelled() {
		this.cancalled = true;
		exception = new ClientException("call is cancelled.");
		failed(exception);
	}

	@Override
	public void failed(Exception ex) {
		this.exception = ex;
		try {
			if (ToolsKit.isNotEmpty(callback)) {
				this.callback.onFail(ex);
				close();
			}
			result.onFail(ex);
		} catch (Exception e) {
			if (ToolsKit.isNotEmpty(callback)) {
				this.callback.onFail(e);
			}
			result.onFail(e);
		}
	}
	
	public boolean isCancelled() {
		return cancalled;
	}
	
	public boolean isSuccess(){
		return success;
	}
	
	public Exception getException() {
		return exception;
	}
	
	public ResponseMessage getResponseMessage() {
		return responseMessage;
	}
	
	public AsyncResult<T> getAsyncResult() {
		return result;
	}

	protected void buildResponseMessage(HttpResponse response) {
		responseMessage = new ResponseMessage();
		if (ToolsKit.isNotEmpty(response.getStatusLine())) {
			responseMessage.setStatusCode(response.getStatusLine().getStatusCode());
		}

		if (ToolsKit.isNotEmpty(response.getEntity())) {
			try {
				responseMessage.setContent(response.getEntity().getContent());
			} catch (Exception e) {
				logger.warn(e.getMessage(), e);
			}
		}

		Header[] headers = response.getAllHeaders();
		if (ToolsKit.isNotEmpty(headers)) {
			Map<String, String> resultHeaders = new HashMap<String, String>(headers.length);
			for (Header header : headers) {
				resultHeaders.put(header.getName(), header.getValue());
			}
			HttpUtil.convertHeaderCharsetFromIso88591(resultHeaders);
		}
		handleResult();
	}

	/**
	 * 请求返回结果处理器
	 */
	private void handleResult() {
		try {
			T obj = null;
			if (ToolsKit.isNotEmpty(resultParser)) {
				obj = this.resultParser.parse(responseMessage);
			}
			if (responseMessage.isSuccessful()) {
				if (ToolsKit.isNotEmpty(callback)) {
					this.callback.onSuccess(obj);
					close();
				}
				result.onSuccess(obj);
				this.success = true;
			} else {
				Exception exception = null;
				if (ToolsKit.isNotEmpty(callback)) {
					exception = new ClientException("response status code is: " + responseMessage.getStatusCode());

				}
				failed(exception);
			}
		} catch (Exception e) {
			try {
				logger.warn(ToolsKit.inputStream2String(responseMessage.getContent()));
			} catch (Exception ex) {
			}
			failed(e);
		} 
	}

	public void close() {
		if (ToolsKit.isNotEmpty(responseMessage)) {
			try {
				this.responseMessage.close();
			} catch (IOException e) {
				logger.warn(e.getMessage(), e);
			}
		}
	}

	static class DefaultAsyncResult<T> implements AsyncResult<T> {

		private ReentrantLock rlock = new ReentrantLock();
		private Condition lock = rlock.newCondition();
		private boolean completed = false;
		private T result = null;
		private boolean success;
		private Exception exception;
		private HttpCallBack<T> callback;

		public DefaultAsyncResult(HttpCallBack<T> callback) {
			this.callback = callback;
		}

		@Override
		public T getResult() {
			return getResult(0);
		}

		@Override
		public T getResult(long waittime) {
			if (!completed) {
				try {
					rlock.lock();
					if (!completed) {
						if (waittime <= 0) {
							lock.await();
						} else {
							lock.await(waittime, TimeUnit.MILLISECONDS);
						}
					}
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				} finally {
					rlock.unlock();
				}
			}
			return result;
		}

		@Override
		public boolean isSuccess() {
			return success;
		}

		@Override
		public Exception getException() {
			return exception;
		}

		public void onSuccess(T result) {
			try {
				rlock.lock();
				this.result = result;
				this.success = true;
				this.completed = true;
				lock.signal();
			} finally {
				rlock.unlock();
			}
		}

		public void onFail(Exception exception) {
			try {
				rlock.lock();
				this.exception = exception;
				this.success = false;
				this.completed = false;
				lock.signal();
			} finally {
				rlock.unlock();
			}
		}
	}

}
